#include <QtDebug>
#include <QtGui>
#include <QtOpenGL>
#include <GL/glut.h>

#include <iostream>
#include <math.h>

#include "parafluiddialogsimulation.h"
#include "parafluidvector.h"
#include "windowglwidget.h"

using namespace ParaFluid;

WindowGLWidget::WindowGLWidget(QWidget * parent)
	: QGLWidget(parent)
	, _simulation()
{
	
   connect(&_simulation, SIGNAL(stepped()), this, SLOT(updateGL()));	

   _scaleFactor = 0.4;
   _cRadius = 10.0; // our radius distance from our character
   _camera = Camera(Vector(-1.5, 1.5, 2.0), Orientation(0.0, -90.0, 0.0));
   
   // some colors
   _Green = QColor::fromCmykF(0.40, 0.0, 1.0, 0.0);
   _LightGray = QColor::fromCmykF(0.2, 0.2, 0.2, 0.2);
   _Black = QColor::fromCmykF(1.0, 1.0, 1.0, 1.0);
   

   // Init FPS counter
   _StepCounter = 0;
   _LastStepCounter = 0;
   _UpdateFPS = false;
   _ShowFPS = false;
   
   connect(&_FpsTimer, SIGNAL(timeout()), this, SLOT(updateFPS()));

   _atomRadius = 0.25;
   
   //initRandomObjectPositions();

}

WindowGLWidget::~WindowGLWidget() {
   
   makeCurrent();
}

void WindowGLWidget::initializeGL() {
   
   qglClearColor(_LightGray);
   
   //glEnable(GL_CULL_FACE);
   glEnable(GL_DEPTH_TEST); //enable the depth testing
   glEnable(GL_LIGHTING); //enable the lighting
   glEnable(GL_LIGHT0); //enable LIGHT0, our Diffuse Light
   glEnable(GL_COLOR_MATERIAL);
   glShadeModel(GL_SMOOTH); //set the shader to smooth shader

}

void WindowGLWidget::resizeGL(int width , int height) {
   
   glViewport(0,0,width,height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective (60, (GLfloat)width / (GLfloat)height, 0.1, 100.0); //set the perspective (angle of sight, width, height, , depth)
   glMatrixMode(GL_MODELVIEW);
   
}

void WindowGLWidget::paintGL() {

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   
   updateCameraGL();
  
   drawWireCube(_simulation.sph().constraints(), Color::RED);

   class AtomDrawer
		: public AtomVoxelizer::ReadOnlyVisitor
	{
		
	public:
		
		void visit(const Atom & atom)
		{
			drawSphere(0.25, 6, 6, atom.currentPosition, atom.substance.color);
		}
		
	} drawer;
   
   _simulation.sph().atomVoxelizer.acceptVisitor(drawer);
   
   _StepCounter++;// To valid! Avant c'etais dans la methode simulate()??

   if(_UpdateFPS && _ShowFPS)
   {
       output(("fps : " + QString::number(_StepCounter)).toAscii().data());
      
      _LastStepCounter = _StepCounter; 
      _StepCounter = 0;
      _UpdateFPS = false; 
   }
   else if(_ShowFPS)
   {
      output(("fps : " + QString::number(_LastStepCounter)).toAscii().data());       
   }

   glFlush();
}

void WindowGLWidget::updateFPS()
{
   _UpdateFPS = true;
}

void WindowGLWidget::output(char * string) 
{
    int len, i;

    // Get the width, height of the windowGLWidget
    int ww = width();
    int wh = height();

    glDisable(GL_DEPTH_TEST);

    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, ww - 1, 0, wh - 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glColor3f(1, 1, 1);
    glDisable(GL_FRAGMENT_PROGRAM_ARB);
    glDisable(GL_VERTEX_PROGRAM_ARB);

    glActiveTexture(GL_TEXTURE0_ARB);
    glDisable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE1_ARB);
    glDisable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE2_ARB);
    glDisable(GL_TEXTURE_3D);
    glActiveTexture(GL_TEXTURE3_ARB);
    glDisable(GL_TEXTURE_2D);

    glRasterPos2i(5, wh-15); // x,y position on the WindowGLWidget

    len = (int) strlen(string);
    
    for (i = 0; i < len; i++) 
    {
	glutBitmapCharacter(GLUT_BITMAP_9_BY_15, string[i]);
    }

    glMatrixMode( GL_PROJECTION );
    glPopMatrix();
    glMatrixMode( GL_MODELVIEW );
    glPopMatrix();

    glEnable(GL_DEPTH_TEST);
}

	
void WindowGLWidget::updateCameraGL()
{
   glTranslated(0.0, 0.0, -_cRadius);
   glRotated(_camera.orientation.x, 1.0, 0.0, 0.0);

   //rotate our camera on the y-axis (up and down)
   glRotated(_camera.orientation.y, 0.0, 1.0, 0.0); 
   
   //translate the screen to the position of our camera
   glTranslated(-_camera.position.x, -_camera.position.y, -_camera.position.z);
   glColor3f(1.0f, 1.0f, 1.0f);

   // scale the scene from (0,0,0)
   glScaled(_scaleFactor, _scaleFactor, _scaleFactor);
}

void WindowGLWidget::mouseDoubleClickEvent(QMouseEvent * /*event*/)
{
	DialogSimulation * dialog = new DialogSimulation(this, &_simulation);
	dialog->show();
}
 
void WindowGLWidget::mousePressEvent(QMouseEvent *event) {
   
   _lastPos = event->pos();
   
}

void WindowGLWidget::mouseMoveEvent(QMouseEvent *event) {
   
   int dx = event->x() - _lastPos.x();
   int dy = event->y() - _lastPos.y();
   
   if (event->buttons() & Qt::LeftButton) 
   {
      setCameraOrientationX(_camera.orientation.x + 0.1 * dy);
      setCameraOrientationY(_camera.orientation.y + 0.1 * dx);
   }
   else if (event->buttons() & Qt::RightButton) 
   {
      setCameraOrientationX(_camera.orientation.x + 0.1 * dy);
      setCameraOrientationZ(_camera.orientation.z + 0.1 * dx);
   }
   
   _lastPos = event->pos();
   
}

void WindowGLWidget::wheelEvent(QWheelEvent * event){
   
   // up scale
   if(static_cast<QWheelEvent *>(event)->delta() > 0)
   {
         uniformScale(_scaleFactor + 0.01);
   }
   // down scale
   else
   {
         uniformScale(_scaleFactor - 0.01);
   }
}

void WindowGLWidget::keyPressEvent(QKeyEvent * event)
{
    switch (event->key()) 
    {
	    
        case Qt::Key_F : // Show/Hide FPS
	    
	    if(_ShowFPS)
	    {
		_FpsTimer.stop();
		_ShowFPS = false;
	    }
	    else
	    {
		_StepCounter = 0;
		_FpsTimer.start(1000);
		_UpdateFPS = true;
		_ShowFPS = true;
	    }

            updateGL();
            break;
	    
        case Qt::Key_O :
            
            _camera.position.y -= 0.1;
            updateGL();
            break;

        case Qt::Key_L :
            
            _camera.position.y += 0.1;
            updateGL();
            break;
            
        case Qt::Key_W :
        case Qt::Key_Up :
            
            _camera.position.z -= 0.1;
            updateGL();
            break;

        case Qt::Key_S :
        case Qt::Key_Down :
            
            _camera.position.z += 0.1;
            updateGL();
            break;

        case Qt::Key_A :
        case Qt::Key_Left :	
            
            _camera.position.x -= 0.05;
            updateGL();
            break;

        case Qt::Key_D :
        case Qt::Key_Right :	
            
            _camera.position.x += 0.05;
            updateGL();
            break;
        
        // Reset Position and Orientation
        case Qt::Key_R :
            
            _camera.orientation.x = 0.0;
            _camera.orientation.y = 0.0;
            _camera.orientation.z = 0.0;
            _camera.position.x = 0.0;
            _camera.position.x = 0.0;
            _camera.position.x = 0.0;
            updateGL();
            break;
    }
}

void WindowGLWidget::setCameraOrientationX(const double & angle) {
   
   double NormAngle = normalizeAngle(angle);
   if (NormAngle != _camera.orientation.x) 
   {
      _camera.orientation.x = NormAngle;
      emit xRotationChanged(NormAngle);
      updateGL();
   }
   
}

void WindowGLWidget::setCameraOrientationY(const double & angle) {
   
   double NormAngle = normalizeAngle(angle);
   if (NormAngle != _camera.orientation.y) 
   {
      _camera.orientation.y = NormAngle;
      emit yRotationChanged(NormAngle);
      updateGL();
   }
   
}

void WindowGLWidget::setCameraOrientationZ(const double & angle) {
   
   double NormAngle = normalizeAngle(angle);
   if (NormAngle != _camera.orientation.z) 
   {
      _camera.orientation.z = NormAngle;
      emit zRotationChanged(NormAngle);
      updateGL();
   }
   
}

double WindowGLWidget::normalizeAngle(const double & angle) {
   
   double normalizedAngle(angle);
   
   while(normalizedAngle < 0)
   {
      normalizedAngle += 360 * 16;      
   }

   while(normalizedAngle > 360 * 16) //5760
   {
      normalizedAngle -= 360 * 16;   
   }
   
   return normalizedAngle;
}

void WindowGLWidget::uniformScale(const double & scaleFactor) {
   
   _scaleFactor = scaleFactor;
   updateGL();
}

void WindowGLWidget::initRandomObjectPositions(int objectQuantity) //set the positions of the cubes
{ 
    if(_RandObjPositions.empty())
    {
        for (int i = 0; i < objectQuantity; i++) 
        {
            _RandObjPositions.insert(i, Vector(rand() % 5 + 1, 0, rand() % 5 + 1));
        }
    }
}

//draw a cube
void WindowGLWidget::drawCube(const double & side, const Vector & position, const Color & color) 
{
    glPushMatrix();
    glTranslated(position.x, position.y, position.z); //translate the cube
    glColor3f(color.red, color.green, color.blue);
    glutSolidCube(side); //draw the cube
    glPopMatrix();
}

void WindowGLWidget::drawWireCube(const ParaFluid::Box & box, const Color & color) 
{
    double extent = box.extent.x + (2.0 * _atomRadius);
    double extentHalf = extent / 2.0;
    glPushMatrix();
    glColor3f(color.red, color.green, color.blue);
    glTranslated(box.position.x - _atomRadius + extentHalf, box.position.y - _atomRadius + extentHalf, box.position.z - _atomRadius + extentHalf);
    glutWireCube(extent);
    glPopMatrix();
}

//draw a sphere
void WindowGLWidget::drawSphere(const double & radius, const int slicesAroundZaxis, const int stacksAlongZaxis, const Vector & position, const Color & color) 
{
    glPushMatrix();
    glTranslated(position.x, position.y, position.z); //translate the sphere
    glColor3f(color.red, color.green, color.blue);
    glutSolidSphere(radius, slicesAroundZaxis, stacksAlongZaxis);
    glPopMatrix();
}

//draw radom cubes
void WindowGLWidget::drawRandomCubes(int cubeQuantity) 
{
    if(_RandObjPositions.empty())
    {
        initRandomObjectPositions(cubeQuantity);
    }
    
    for(int i = 0; i < _RandObjPositions.size(); ++i) 
    {
        drawCube(_RandObjPositions.at(i).x/2, 
                 Vector(-_RandObjPositions.at(i).x*10, 
                          0,
                          -_RandObjPositions.at(i).z*10),
                 Color::BLUE);
    }
}

//draw radom spheres
void WindowGLWidget::drawRandomSpheres(int sphereQuantity) 
{
    if(_RandObjPositions.empty())
    {
        initRandomObjectPositions(sphereQuantity);
    }
    
    for(int i = 0; i < _RandObjPositions.size(); ++i) 
    {
        drawSphere(_RandObjPositions.at(i).x/2, 
                   6, 
                   6, 
                   Vector(-_RandObjPositions.at(i).x*5, 
                            0,
                            -_RandObjPositions.at(i).z*5),
                   Color::GREEN);
    }
}
